﻿using Microsoft.CSharp;
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace SoftGL
{
    abstract class Shader
    {
        public static Shader Create(ShaderType type, uint id)
        {
            Shader result = null;
            switch (type)
            {
                case ShaderType.VertexShader: result = new VertexShader(id); break;
                case ShaderType.TessControlShader: result = new TessControlShader(id); break;
                case ShaderType.TessEvaluationShader: result = new TessEvaluationShader(id); break;
                case ShaderType.GeometryShader: result = new GeometryShader(id); break;
                case ShaderType.FragmentShader: result = new FragmentShader(id); break;
                case ShaderType.ComputeShader: result = new ComputeShader(id); break;
                default:
                    throw new NotImplementedException();
            }

            return result;
        }

        /// <summary>
        /// Compiling error information.
        /// </summary>
        private string infoLog = string.Empty;
        /// <summary>
        /// methods.
        /// </summary>
        protected CompilerResults compiledCode;
        protected Type codeType;
        protected Dictionary<string, UniformVariable> uniformVariableDict = new Dictionary<string, UniformVariable>();

        /// <summary>
        /// Name -> field.
        /// </summary>
        public Dictionary<string, UniformVariable> UniformVariableDict { get { return uniformVariableDict; } }

        /// <summary>
        /// Compiling error information.
        /// </summary>
        public string InfoLog { get { return infoLog; } }

        /// <summary>
        /// Source code of shader.
        /// </summary>
        public string Code { get; set; }

        public ShaderType ShaderType { get; private set; }

        /// <summary>
        /// name generated by glCreateShader().
        /// </summary>
        public uint Id { get; private set; }

        /// <summary>
        /// Creates a program object.
        /// </summary>
        /// <param name="shaderType"></param>
        /// <param name="id"></param>
        public Shader(ShaderType shaderType, uint id)
        {
            this.ShaderType = shaderType;
            this.Id = id;
        }

        /// <summary>
        ///
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return string.Format("Shader: Id:{0}", this.Id);
        }

        protected abstract string AfterCompile();

        public void Compile()
        {
            var options = new Dictionary<string, string>();
            options.Add("CompilerVersion", "v3.5");
            var compiler = new CSharpCodeProvider(options);
            var compParameters = new CompilerParameters();
            //compParameters.ReferencedAssemblies.Add("system.core.dll");
            compParameters.ReferencedAssemblies.Add("SoftGL.dll");
            compParameters.ReferencedAssemblies.Add("SoftGL.ShadingLanguage.dll");
            CompilerResults res = compiler.CompileAssemblyFromSource(compParameters, this.Code);
            if (res.Errors.Count > 0) { this.infoLog = DumpLog(res); return; }
            Type codeType = this.FindShaderCodeType(res.CompiledAssembly, this.ShaderType.GetShaderCodeType());
            if (codeType == null) { this.infoLog = string.Format("No {0} found!", this.ShaderType); return; }
            string result = FindUniformVariables(codeType, this.uniformVariableDict);
            if (result != string.Empty) { this.infoLog = result; return; }

            this.compiledCode = res;
            this.codeType = codeType;

            this.infoLog = this.AfterCompile();
        }

        /// <summary>
        /// Creates an instance of executable shader code.
        /// </summary>
        /// <returns></returns>
        public abstract object CreateCodeInstance();

        private string FindUniformVariables(Type shaderCodeType, Dictionary<string, UniformVariable> dict)
        {
            dict.Clear();
            foreach (var item in shaderCodeType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly))
            {
                object[] inAttribute = item.GetCustomAttributes(typeof(UniformAttribute), false);
                if (inAttribute != null && inAttribute.Length > 0) // this is a 'uniform ...;' field.
                {
                    var v = new UniformVariable(item);
                    dict.Add(item.Name, v);
                }
            }

            return string.Empty;
        }

        protected Type FindShaderCodeType(Assembly assembly, Type shaderCodeType)
        {
            Type result = null;
            Type[] types = assembly.GetTypes();
            foreach (var item in types)
            {
                if (item.BaseType == shaderCodeType)
                {
                    result = item;
                    break;
                }
            }

            return result;
        }

        public void GetShaderStatus(ShaderStatus pname, int[] pValues)
        {
            if (pValues == null || pValues.Length < 1) { return; }

            switch (pname)
            {
                case ShaderStatus.ShaderType:
                    pValues[0] = (int)this.ShaderType;
                    break;
                case ShaderStatus.DeleteStatus:
                    throw new NotImplementedException();
                case ShaderStatus.CompileStatus:
                    pValues[0] = this.infoLog.Length == 0 ? (int)GL.GL_TRUE : (int)GL.GL_FALSE;
                    break;
                case ShaderStatus.InfoLogLength:
                    pValues[0] = this.infoLog.Length;
                    break;
                case ShaderStatus.ShaderSourceLength:
                    string code = this.Code;
                    if (code == null) { pValues[0] = 0; }
                    else { pValues[0] = code.Length; }
                    break;
                default:
                    throw new NotImplementedException();
            }
        }

        protected string DumpLog(CompilerResults res)
        {
            var builder = new StringBuilder();
            if (res != null)
            {
                foreach (var item in res.Errors)
                {
                    builder.AppendLine(item.ToString());
                }
            }

            return builder.ToString();
        }

    }
}
